AOT-safe auth provider with feature switch and trimmer support#4348
AOT-safe auth provider with feature switch and trimmer support#4348paulmedynski wants to merge 10 commits into
Conversation
- Make SqlAuthenticationProviderManager public with static GetProvider/SetProvider API - Add FeatureSwitchDefinition for reflection-based provider discovery (Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery) - Add ILLink.Substitutions.xml for net8.0 trimmer support - Add AotCompatibility test app (tools/AotCompatibility) validating Native AOT publish and trimmer behavior - Update ref assembly (no nullable annotations, per codebase convention) - Add unit tests for provider manager public API
There was a problem hiding this comment.
Pull request overview
This PR exposes an AOT-friendly public authentication provider registration surface (SqlAuthenticationProviderManager) and introduces a feature switch to enable trimming away reflection-based Azure extension provider discovery for NativeAOT/trimming scenarios.
Changes:
- Made
SqlAuthenticationProviderManagerpublic withGetProvider/SetProviderAPIs and addedApplicationClientIdas part of the public surface. - Added
Microsoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscoveryfeature switch with trimmer support ([FeatureSwitchDefinition]on .NET 9+ andILLink.Substitutions.xmlfor .NET 8). - Added an
tools/AotCompatibilitytest app plus new/updated unit & functional tests covering the new public API and config behavior.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/AotCompatibility/README.md | Documents the AOT compatibility test app and feature switch usage. |
| tools/AotCompatibility/Program.cs | Implements runtime checks and trimming verification via the ILC map file. |
| tools/AotCompatibility/Directory.Packages.props | Enables CPM for the tool (project-mode, no package versions). |
| tools/AotCompatibility/Directory.Build.props | Prevents inheriting repo-wide build props for the tool. |
| tools/AotCompatibility/AotCompatibility.csproj | Adds the NativeAOT/trimming test app and propagates the feature switch via RuntimeHostConfigurationOption. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlAuthenticationProviderManagerTests.cs | Adds unit tests for the new public manager API and interop with Abstractions. |
| src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs | Extends default-switch tests to cover the new auth discovery switch. |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlAuthenticationProviderManagerTests.cs | Adjusts tests (pragma) and adds validation for reading ApplicationClientId from config. |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/app.config | Adds applicationClientId attribute to the auth provider config section. |
| src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs | Adds pragma suppression around newly-obsoleted Abstractions API usage. |
| src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs | Adds helper plumbing to reset the new switch in tests. |
| src/Microsoft.Data.SqlClient/src/Resources/ILLink.Substitutions.xml | Adds substitutions for the new feature switch (and retains UseManagedNetworking stubs). |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs | Makes the type public and guards reflection-based discovery behind the new switch; adds AOT/trimming annotations. |
| src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs | Introduces the new switch property and [FeatureSwitchDefinition] for .NET 9+. |
| src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj | Embeds ILLink.Substitutions.xml for all non-net462 TFMs. |
| src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.cs | Adds the new public API to the reference assembly (without nullable annotations). |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.Internal.cs | Updates reflection binding flags to target public manager methods. |
| src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/SqlAuthenticationProvider.cs | Marks legacy Abstractions APIs obsolete in favor of the new manager APIs. |
| doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml | Adds public XML doc content for the new manager API. |
Comments suppressed due to low confidence (1)
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs:346
- SetProvider is now a public API but does not validate the provider argument; passing null currently results in a NullReferenceException. Public APIs should throw ArgumentNullException for null arguments.
/// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/SetProvider/*'/>
public static bool SetProvider(SqlAuthenticationMethod authenticationMethod, SqlAuthenticationProvider provider)
{
if (!provider.IsSupported(authenticationMethod))
{
throw SQL.UnsupportedAuthenticationByProvider(authenticationMethod.ToString(), provider.GetType().Name);
- Fix SetProvider exception docs: SqlException → NotSupportedException - Clarify AOT remarks: static ctor uses reflection by default; describe how to disable it for full AOT compatibility - Return non-zero exit code on SqlConnection construction failure - Replace Directory.GetFiles with EnumerateFiles(IgnoreInaccessible) - Clarify README: 'the test app default' vs library default - Add XML summary docs to all new unit test methods - Remove [Obsolete] from SqlAuthenticationProvider.GetProvider/SetProvider; add remarks pointing to SqlAuthenticationProviderManager equivalents noting future deprecation and removal - Remove corresponding #pragma CS0618 suppressions from tests - Document applicationClientId in test app.config
paulmedynski
left a comment
There was a problem hiding this comment.
I decided against the obsoletion.
Separate UseManagedNetworkingOnWindows entries into a Windows-only ILLink.Substitutions.Windows.xml file to prevent a breaking change for cross-platform consumers who set the switch to false expecting it to be a no-op on Unix. Add unit tests verifying the correct substitution files are embedded per platform.
… doc fixes - Wrap `using System.Diagnostics.CodeAnalysis` in #if NET (unused on net462) - Gate ILLink substitution tests by TFM; add NETFRAMEWORK negative assertions - Fix remarks: "on all TFMs" -> "on .NET 8+" (net462 has no trimmer) - Add -f/-r flags to AotCompatibility README publish examples
|
Addressed all actionable review feedback in 11d75ab:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4348 +/- ##
==========================================
- Coverage 65.39% 63.42% -1.98%
==========================================
Files 285 280 -5
Lines 43331 66217 +22886
==========================================
+ Hits 28337 41999 +13662
- Misses 14994 24218 +9224
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Remove 'using Microsoft.Data.SqlClient.Tests.Common;' from SqlAuthenticationProviderManagerTests.cs - Remove 'using System.Reflection;' from ILLinkSubstitutionsTests.cs Both would cause CS8019 with warnings-as-errors.
Restructure UseManagedNetworking so it no longer needs a per-OS ILLink substitution file: public static bool UseManagedNetworking => The platform guard guarantees managed SNI on non-Windows regardless of the trimmer-substituted value of UseManagedNetworkingOnWindows, making it safe to embed the substitution entries in the cross-platform ILLink.Substitutions.xml on all platforms. Changes: - LocalAppContextSwitches: split UseManagedNetworking into a public expression-bodied property with platform guard + private UseManagedNetworkingOnWindows property (trimmer target) - Widen UseManagedNetworkingOnWindowsString and cache field from '#if NET && _WINDOWS' to '#if NET' - Merge ILLink.Substitutions.Windows.xml entries into the cross-platform ILLink.Substitutions.xml (targeting get_UseManagedNetworkingOnWindows instead of get_UseManagedNetworking) - Delete ILLink.Substitutions.Windows.xml - Remove conditional Windows-only embedding from csproj - Update ILLinkSubstitutionsTests to match: single cross-platform resource on all .NET TFMs, no Windows-specific resource
| public sealed class SqlAuthenticationProviderManager | ||
| { | ||
| /// <include file='../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProviderManager.xml' path='docs/members[@name="SqlAuthenticationProviderManager"]/ApplicationClientId/*'/> | ||
| public static string ApplicationClientId { get { throw null; } } |
There was a problem hiding this comment.
This shouldn't be public, it's an optional and in absence driver's own will be picked automatically.
There was a problem hiding this comment.
The docs explain why it is public - for AOT apps that want to continue using our Provider, but need a custom app ID that only the Manager knows (i.e. read from config). In the automatic-registration-reflection flow, the Manager supplies it to the Provider's constructor. In the (new) manual-registration flow, the app would have to read it from the same config if this wasn't public. That's an additional burden that we didn't want to impose.
There was a problem hiding this comment.
It should be internally set by default if no application id is provided - like it does today. No user config is needed to provide driver's own app client id.
There was a problem hiding this comment.
This property isn't our 1st party application client ID. It's whatever custom ID the Manager found by reading app config files, or null if none was found.
In reflection-mode, the Manager then supplies this to the Provider constructor. If null, the Provider uses our 1st party ID. That's the "normal" flow, both before 7.0, and since 7.0 including these changes.
In the new AOT-mode, the app is responsible for constructing the Provider. If the app provides null (or uses the default constructor), then it gets our 1st party ID. However, if the app was previously relying on the Manager to read app config and supply a custom ID, without this public property it has no way to get that custom ID, unless it starts reading app configs itself.
There was a problem hiding this comment.
Discussed internally. I will make this an internal property for unit tests.
|
@paulmedynski This pull request has been marked as Author attention needed. When you have addressed the reviewer feedback and are ready for another review, please post a comment with |
- Rename s_useManagedNetworking → s_useManagedNetworkingOnWindows in LocalAppContextSwitchesHelper to match field rename in LocalAppContextSwitches (3 sites: ctor, Dispose, property) - Use parameterless ActiveDirectoryAuthenticationProvider ctor when ApplicationClientId is null to preserve built-in default client ID - Add -f net9.0 -r linux-x64 to AOT README publish-with-reflection example
- Remove the public static ApplicationClientId property from the Manager - Remove it from the ref assembly and XML docs - Skip app.config loading when reflection-based discovery is disabled - Split into two constructors: no-arg (AOT-safe) and object-arg (config-driven) - Update AotCompatibility test app to supply a random GUID as client ID - Remove related unit and functional tests
- Stream ILC map file line-by-line instead of File.ReadAllText to avoid memory pressure from large map files (tools/AotCompatibility) - Add warning when AppContext feature switch is absent (expected in JIT mode) - Fix stale app.config comment referencing removed public ApplicationClientId - Move app.config auth provider tests from FunctionalTests to UnitTests - Add separate token-acquisition test for config-registered provider - Remove duplicate IsDummySqlAuthenticationProviderSetByDefault test, its DummySqlAuthenticationProvider, and app.config from FunctionalTests - Add internal ApplicationClientId property for test verification
|
We have another design approach that we would like to explore, so pausing this PR for the time being. |
Summary
Make
SqlAuthenticationProviderManagerpublic with a staticGetProvider/SetProviderAPI and add trimmer/AOT support via a feature switch.Changes
SqlAuthenticationProviderManager.GetProvider()andSetProvider()as the public surface for authentication provider registrationMicrosoft.Data.SqlClient.EnableReflectionBasedAuthenticationProviderDiscovery[FeatureSwitchDefinition]on .NET 9+ for linker-integrated trimmingILLink.Substitutions.xmlfor .NET 8 trimmer supportfalse, the reflection-basedLoadAzureExtensionProvider()is trimmed awayILLink.Substitutions.xml(embedded on all non-net462 TFMs) contains both the auth provider feature switch andUseManagedNetworkingOnWindowsentries. TheUseManagedNetworkingproperty uses a runtime platform guard (!OperatingSystem.IsWindows() || UseManagedNetworkingOnWindows) so the trimmer substitution ofUseManagedNetworkingOnWindowsis safe on all platforms — Unix always returnstrueregardless of the substituted value.tools/AotCompatibility/): Validates Native AOT publish succeeds and verifies trimmer removes reflection code when feature switch is disabledILLinkSubstitutionsTestsverifying correct embedded resources per platformVerification
dotnet build -t:Buildsucceeds (0 warnings, 0 errors)dotnet publishwith Native AOT succeeds for net9.0/net10.0LoadAzureExtensionProvideris trimmed when feature switch isfalseILLink.Substitutions.xmlembedded in assembly on all platformsNotes
SspiAuthenticationParameters, column encryption providers)